/*
 * Javlov - a Java toolkit for reinforcement learning with multi-agent support.
 * 
 * Copyright (c) 2009 Matthijs Snel
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package net.javlov.world.grid;

import java.awt.Point;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;

import net.javlov.world.Body;


/**
 * 
 * @author University of Utrecht
 * @author Matthijs Snel
 * @version 0.2, 28/05/2007
 */
public class GridCell extends Rectangle2D.Double {
    // basic data

    protected boolean border, atRim;

    // navigation
    
    protected GridCell[] neighbour;
    
    protected List<Body> occupiers;
    
    protected Point position;
    
    public Point getPosition() {
		return position;
	}

	public void setPosition(Point position) {
		this.position = position;
	}

	/**
     * Constructor that sets up an
     * array to keep track of each fields neigbours.  
     */
    public GridCell() {
    	this(0,0,0,0);
    }
    
    public GridCell(double x, double y, double w, double h) {
    	super(x, y, w, h);
    	occupiers = new ArrayList<Body>();
    	neighbour = new GridCell[8];
    	border = false;
    	atRim = false;
    }
    
    public void addBody(Body b) {
    	occupiers.add(b);
    }
    
    public void removeBody(Body b) {
    	occupiers.remove(b);
    }
    
    /**
     * Tests whether the shape of this body is completely contained within the grid cell area.
     * Note the difference in functionality with hasBody(Body).
     * 
     * @param b
     * @return
     */
    public boolean containsBody(Body b) {
    	return super.contains(b.getFigure().getBounds2D());
    }
    
    /**
     * Tests whether the body is an occupier of this grid cell, i.e. if it occurs in the list of
     * bodies that this grid cell contains.
     * Note the difference in functionality with containsBody(Body).
     * 
     * @param b
     * @return
     */
    public boolean hasBody(Body b) {
    	return occupiers.contains(b);
    }
    
    
    public List<Body> getOccupiers() {
    	return occupiers;
    }
    
    /**
     * Returns the 3 neighbouring cells to the quadrant of the cell in which the point lies.
     * Returns null if the point lies exactly in the centre of the cell.
     * 
     * @param x
     * @param y
     * @return
     */
    public GridCell[] getQuadrantNeightbours(double x, double y) {
    	double cx = getCenterX(),
    			cy = getCenterY();
    	//the highly unlikely case that it's exactly at the centre
    	if ( x == cx && y == cy )
    		return null;
    	
    	GridCell[] nb = new GridCell[3];
    	int start;
    	//to the east
    	if ( x >= cx ) {
    		//to the south
    		if ( y >= cy ) {
    			start = Direction.EAST;
    		} else {
    			start = Direction.NORTH;
    		}
    	}
    	//to the west
    	else {
    		//to the south
    		if ( y >= cy ) {
    			start = Direction.SOUTH;
    		} else {
    			start = Direction.WEST;
    		}
    	}
    	
    	for ( int i = 0; i < 3; i++ )
    		nb[i] = neighbour[(start+i)%8];

    	return nb;
    }
    
    public void clear() {
    	occupiers.clear();
    }
    
    // border

    /**
     * Sets border to be true. Field that are on the edge of the grid are border fields.
     */
    public void setBorder() {
    	border = true;
    }

    /**
     * Returns true if the field is a border
     */
    public boolean isBorder() {
    	return border;
    }
    
    /**
     * Returns true if the neighbouring cell in the specified direction is a border and not 
     * actually on the board
     */
    public boolean isAtRim(int direction) {
    	return neighbour[direction].isBorder();
    }
    
    /**
     * Returns true if one of the neighbours is a border and not 
     * actually on the board
     */
    public boolean isAtRim() {
    	return atRim;
    }
    
    /**
     * Checks whether the provided cell is a neighbour of this cell.
     * @param cell the cell to check for
     * @return true if neighbour, false otherwise
     */
    public boolean isNeighbour(GridCell cell) {
    	for ( int i = 0; i < neighbour.length; i++ )
    		if ( neighbour[i].equals(cell) )
    			return true;
    	return false;
    }
    
    public GridCell[] getNeighbours() {
    	return neighbour;
    }
    
    /**
     * Gets the neighbouring field in the direction given as parameter
     */
    public GridCell getNeighbour(int direction) {
    	return neighbour[direction];
    }

    /**
     * Gets the neighbouring field in the direction given as parameter
     */
    public GridCell go(Direction d) {
    	return d.go(this);
    }
    
    /**
     * Sets the field, given as the second parameter, to be the neighbour 
     * in the direction given as the first parameter
     */
    public void setNeighbour(int direction, GridCell n) {
    	neighbour[direction] = n;
    	if ( n.isBorder() )
    		atRim = true;
    }
    
    public String toString() {
    	if ( isBorder() )
    		return "B";
    	return position.toString();
    }
}

